﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
using Relay.UI.Controls.Docking.Primitives;
using System.Reflection;
using Relay.UI.Controls.Win32;
using System.Diagnostics;
using Relay.UI.Controls.Utility;

namespace Relay.UI.Controls.Docking {
    /// <summary>
    /// Represents a group of DockWindows that are hosted in a SplitPanel.
    /// </summary>
    [TemplatePart(Name = "PART_Titlebar", Type = typeof(FrameworkElement)),
     TemplatePart(Name = "PART_Content", Type = typeof(ContentPresenter)),
     TemplatePart(Name = "PART_WindowList", Type = typeof(DockWindowTabPanel))]
    public class DockWindowGroup : Selector, IDockHierarchyObject, IDropTarget {
        #region Fields
        private FrameworkElement _titlebar = null;
        private bool _dragging = false;
        private Point _clickPosition = new Point(0,0);
        private ContentPresenter _contentPresenter = null;
        private Size _floatedSize = new Size(200, 400);
        private double _dockedSize = 0;
        private DateTime _mousedownTime = DateTime.Now;
        private DockWindowTabPanel _tabPanel = null;
        #endregion

        #region Commands
        /// <summary>
        /// Identifies the TogglePinnedStatus command.
        /// </summary>
        public static readonly RoutedCommand TogglePinnedStatusCommand = new RoutedCommand("TogglePinnedStatus",
            typeof(DockWindowGroup));

        public static readonly RoutedCommand CloseActiveWindowCommand = new RoutedCommand("CloseActiveWindow",
            typeof(DockWindowGroup));
        #endregion

        #region Dependency Properties
        private static readonly DependencyPropertyKey IsFocusedInDockManagerPropertyKey =
            DependencyProperty.RegisterReadOnly("IsFocusedInDockManager", typeof(bool), typeof(DockWindowGroup),
            new FrameworkPropertyMetadata(false));

        private static readonly DependencyPropertyKey HasMultipleVisibleItemsPropertyKey =
            DependencyProperty.RegisterReadOnly("HasMultipleVisibleItems", typeof(bool), typeof(DockWindowGroup),
            new FrameworkPropertyMetadata(false));

        /// <summary>
        /// Identifies the IsDockFocused dependency property.
        /// </summary>
        public static readonly DependencyProperty IsFocusedInDockManagerProperty =
            IsFocusedInDockManagerPropertyKey.DependencyProperty;

        /// <summary>
        /// Identifies the ZeroOrOneItems dependency property.
        /// </summary>
        public static readonly DependencyProperty HasMultipleVisibleItemsProperty =
            HasMultipleVisibleItemsPropertyKey.DependencyProperty;

        /// <summary>
        /// Identifies the SelectedContent dependency property.
        /// </summary>
        public static readonly DependencyProperty SelectedContentProperty = DependencyProperty.Register(
            "SelectedContent", typeof(UIElement), typeof(DockWindowGroup));
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes the <see cref="DockWindowGroup"/> class.
        /// </summary>
        static DockWindowGroup() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DockWindowGroup),
                new FrameworkPropertyMetadata(typeof(DockWindowGroup)));

            CommandManager.RegisterClassCommandBinding(typeof(DockWindowGroup), new CommandBinding(
                TogglePinnedStatusCommand, new ExecutedRoutedEventHandler(ExecuteTogglePinnedStatus)));
            CommandManager.RegisterClassCommandBinding(typeof(DockWindowGroup), new CommandBinding(
                CloseActiveWindowCommand, new ExecutedRoutedEventHandler(ExecuteCloseActiveWindow)));

            VisibilityProperty.AddOwner(typeof(DockWindowGroup), new FrameworkPropertyMetadata(Visibility.Visible,
                FrameworkPropertyMetadataOptions.AffectsParentMeasure));

            FocusableProperty.OverrideMetadata(typeof(DockWindowGroup), new FrameworkPropertyMetadata(false));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DockWindowGroup"/> class.
        /// </summary>
        public DockWindowGroup() {
            // This is extremely evil as it bypasses the encapsulation of the Selector class by manually invoking
            // an internal property change operation. Nevertheless, I assume that CanSelectMultiple is supposed to be
            // protected rather than internal, since without it, derived classes can't specify single selection mode
            // whereas the built-in .NET types can (and do, with a simple base.CanSelectMultiple = false) because they
            // are in the same assembly.
            //
            // There will supposedly be a new MultiSelector base class that will address this issue.
            typeof(Selector).InvokeMember("CanSelectMultiple",
                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetProperty, null, this,
                new object[] { false });
            // End of evil code.
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the content of the selected item.
        /// </summary>
        /// <value>The content of the selected item.</value>
        public UIElement SelectedContent {
            get { return (UIElement)GetValue(SelectedContentProperty); }
            internal set { SetValue(SelectedContentProperty, value); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance has zero or one items.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has zero or one items; otherwise, <c>false</c>.
        /// </value>
        public bool HasMultipleVisibleItems {
            get { return (bool)GetValue(HasMultipleVisibleItemsProperty); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is focused in the dock manager.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is focused in the dock manager; otherwise, <c>false</c>.
        /// </value>
        public bool IsFocusedInDockManager {
            get { return (bool)GetValue(IsFocusedInDockManagerProperty); }
        }

        internal Size FloatedSize {
            get { return _floatedSize; }
            set { _floatedSize = value; }
        }

        internal double DockedSize {
            get { return _dockedSize; }
            set { _dockedSize = value; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Shows this DockWindowGroup.
        /// </summary>
        public void Show() {
            IDockHierarchyObjectHost parent = Parent as IDockHierarchyObjectHost;
            if (parent != null) {
                parent.MakeVisible(true);
            }

            Visibility = Visibility.Visible;
        }

        /// <summary>
        /// Hides this DockWindowGroup.
        /// </summary>
        public void Hide(bool hideChildren) {
            IDockHierarchyObjectHost parent = Parent as IDockHierarchyObjectHost;
            if (parent != null) parent.MakeDirty();

            Visibility = Visibility.Collapsed;

            if (hideChildren) {
                foreach (DockWindow w in Items)
                    w.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// Hides the active window.
        /// </summary>
        public void HideActiveWindow() {
            DockWindow window = SelectedItem as DockWindow;
            if (window != null) HideWindow(window);
        }

        /// <summary>
        /// Hides the specified window.
        /// </summary>
        /// <param name="window">The window to hide.</param>
        public void HideWindow(DockWindow window) {
            window.Visibility = Visibility.Collapsed;
            NotifyChildVisibiltyChanged(window);
            
            if (SelectedItem == window) {
                int newSelection = GetNextVisibleIndex();

                if (newSelection == -1) {
                    Hide(true);
                } else {
                    SelectedIndex = newSelection;
                }
            }
        }
        #endregion

        #region Private Methods
        private void Close() {
            IDockHierarchyObjectHost parent = Parent as IDockHierarchyObjectHost;

            if (parent != null) {
                parent.RemoveChild(this);
            }
        }

        private int GetNextVisibleIndex() {
            for (int i = SelectedIndex + 1; i < Items.Count; i++) {
                DockWindow w = Items[i] as DockWindow;
                if (w != null && w.Visibility == Visibility.Visible) return i;
            }

            for (int i = SelectedIndex - 1; i >= 0; i--) {
                DockWindow w = Items[i] as DockWindow;
                if (w != null && w.Visibility == Visibility.Visible) return i;
            }

            return -1;
        }

        private bool HasMultipleVisibleItems_ComputeValue() {
            int count = 0;

            foreach (DockWindow window in Items) {
                if (window.Visibility == Visibility.Visible) count++;
            }

            return count > 1;
        }

        private void ManagerFocusedWindowGroupChanged(object sender, EventArgs e) {
            DockManager manager = (DockManager)sender;
            SetValue(IsFocusedInDockManagerPropertyKey, this == manager.FocusedWindowGroup);
        }

        private void ExecuteTogglePinnedStatus(ExecutedRoutedEventArgs e) {

        }

        private void ExecuteCloseActiveWindow(ExecutedRoutedEventArgs e) {
            HideActiveWindow();
        }
        #endregion

        #region Internal Methods
        internal FloatingWindow CreateFloatingWindow() {
            DockManager owner = ((IDockHierarchyObject)this).GetDockManager();
            /*IDockHierarchyObjectHost parent = Parent as IDockHierarchyObjectHost;

            if (parent != null)
                parent.RemoveChild(this);*/

            return new FloatingWindow(this, owner);
        }

        internal void NotifyItemClicked(DockWindow item) {
            item.IsSelected = true;
            UpdateLayout();
            if (_contentPresenter != null) _contentPresenter.MoveFocus(
                new TraversalRequest(FocusNavigationDirection.First));
        }

        internal void NotifyChildVisibiltyChanged(DockWindow item) {
            SetValue(HasMultipleVisibleItemsPropertyKey, HasMultipleVisibleItems_ComputeValue());
        }
        #endregion

        #region Command Handlers
        private static void ExecuteTogglePinnedStatus(object o, ExecutedRoutedEventArgs e) {
            (o as DockWindowGroup).ExecuteTogglePinnedStatus(e);
        }

        private static void ExecuteCloseActiveWindow(object o, ExecutedRoutedEventArgs e) {
            (o as DockWindowGroup).ExecuteCloseActiveWindow(e);
        }
        #endregion

        #region Event Handlers
        private void TitlebarMouseMove(object sender, MouseEventArgs e) {
            Point pos = e.GetPosition(_titlebar);
            Point mousePos = _titlebar.PointToScreen(pos);
            bool doFloat = (Math.Abs(pos.X - _clickPosition.X) > SystemParameters.MinimumHorizontalDragDistance) ||
                (Math.Abs(pos.Y - _clickPosition.Y) > SystemParameters.MinimumVerticalDragDistance);

            if (_dragging && doFloat) {
                _dragging = false;
                Mouse.Capture(null);
                DockManager manager = ((IDockHierarchyObject)this).GetDockManager();
               
                FloatingWindow window = CreateFloatingWindow();
                window.BeginInit();

                window.Left = mousePos.X - window.Width / 2;
                window.Top = mousePos.Y - SystemParameters.SmallCaptionHeight / 2;

                window.EndInit();
                window.Show();

                manager.DockService.Start(window);
            }
        }

        private void TitlebarMouseUp(object sender, MouseButtonEventArgs e) {
            _dragging = false;
            Mouse.Capture(null);
        }

        private void TitlebarMouseDown(object sender, MouseButtonEventArgs e) {
            if ((DateTime.Now - _mousedownTime).Ticks / 10000 < Interop.GetDoubleClickTime()) {
                Point mousePos = _titlebar.PointToScreen(e.GetPosition(_titlebar));
                // Doubleclicked
                FloatingWindow window = CreateFloatingWindow();

                window.Left = mousePos.X - window.Width / 2;
                window.Top = mousePos.Y - SystemParameters.SmallCaptionHeight / 2;

                window.Show();
            } else {
                if (!IsKeyboardFocusWithin && _contentPresenter != null)
                    _contentPresenter.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));

                _mousedownTime = DateTime.Now;

                if (e.LeftButton == MouseButtonState.Pressed) {
                    _titlebar.CaptureMouse();
                    _clickPosition = e.GetPosition(_titlebar);
                    _dragging = true;
                }
            }
        }
        #endregion

        #region Overrides
        protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e) {
            base.OnPreviewGotKeyboardFocus(e);
            if (!IsFocusedInDockManager)
                ((IDockHierarchyObject)this).GetDockManager().NotifyGroupFocused(this);
        }

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes call
        /// <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
        /// </summary>
        public override void OnApplyTemplate() {
            base.OnApplyTemplate();

            if (_titlebar != null) {
                _titlebar.MouseDown -= new MouseButtonEventHandler(TitlebarMouseDown);
                _titlebar.MouseUp -= new MouseButtonEventHandler(TitlebarMouseUp);
                _titlebar.MouseMove -= new MouseEventHandler(TitlebarMouseMove);
            }

            _titlebar = Template.FindName("PART_Titlebar", this) as FrameworkElement;
            _contentPresenter = Template.FindName("PART_Content", this) as ContentPresenter;
            _tabPanel = Template.FindName("PART_WindowList", this) as DockWindowTabPanel;

            if (_titlebar != null) {
                _titlebar.MouseDown += new MouseButtonEventHandler(TitlebarMouseDown);
                _titlebar.MouseUp += new MouseButtonEventHandler(TitlebarMouseUp);
                _titlebar.MouseMove += new MouseEventHandler(TitlebarMouseMove);
            }
        }

        protected override DependencyObject GetContainerForItemOverride() {
            return new DockWindow();
        }

        protected override bool IsItemItsOwnContainerOverride(object item) {
            return (item is DockWindow);
        }

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item) {
            base.PrepareContainerForItemOverride(element, item);

            if (item is DockWindow) return;

            DockWindow window = element as DockWindow;
            window.Content = item as FrameworkElement;
        }

        protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
            base.OnItemsChanged(e);

            if (Items.Count == 0) {
                Close();
            }

            SetValue(HasMultipleVisibleItemsPropertyKey, HasMultipleVisibleItems_ComputeValue());
        }

        protected override void OnSelectionChanged(SelectionChangedEventArgs e) {
            base.OnSelectionChanged(e);

            DockWindow window = SelectedItem as DockWindow;
            if (window == null)
                SelectedIndex = GetNextVisibleIndex();
            else
                SelectedContent = window.Content;
        }

        protected override void OnInitialized(EventArgs e) {
            base.OnInitialized(e);

            if (Items.Count > 0)
                SelectedIndex = 0;

            DockManager manager = ((IDockHierarchyObject)this).GetDockManager();
            if (manager != null)
                manager.FocusedWindowGroupChanged += new EventHandler(ManagerFocusedWindowGroupChanged);
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {
            SplitPanel parent = Parent as SplitPanel;
            if (parent != null) {
                if (parent.Floating) {
                    _floatedSize = new Size(parent.ActualWidth, parent.ActualHeight);
                } else {
                    if (parent.RootPanel.Orientation == Orientation.Horizontal) {
                        _dockedSize = sizeInfo.NewSize.Height;
                    } else {
                        _dockedSize = sizeInfo.NewSize.Width;
                    }
                }
            }

            base.OnRenderSizeChanged(sizeInfo);
        }
        #endregion

        #region IDockHierarchyObject Members
        DockManager IDockHierarchyObject.GetDockManager() {
            IDockHierarchyObject parent = Parent as IDockHierarchyObject;
            if (parent == null) return null;

            return parent.GetDockManager();
        }

        IDropTarget[] IDockHierarchyObject.FindDropTargets() {
            return Items.OfType<DockWindow>().ToArray();
        }
        #endregion

        #region IDropTarget Members
        private DockElementOverlayAdorner _overlay = null;
        private DockHintAdorner _hint = null;
        private DockOverlayHitTestResult _result = DockOverlayHitTestResult.None;

        private void RemoveOverlay() {
            if (_overlay != null) {
                AdornerLayer layer = VisualTreeHelper.GetParent(_overlay) as AdornerLayer;
                if (layer != null)
                    layer.Remove(_overlay);
                _overlay = null;
            }
        }

        private void RemoveHint() {
            if (_hint != null) {
                AdornerLayer layer = VisualTreeHelper.GetParent(_hint) as AdornerLayer;
                if (layer != null)
                    layer.Remove(_hint);
                _hint = null;
            }
        }

        bool IDropTarget.Update(FloatingWindow window, Point mousePos, bool disable) {
            if (this.PointInsideElement(mousePos)) {
                if (_overlay == null) {
                    AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);
                    _overlay = new DockElementOverlayAdorner(this);
                    layer.Add(_overlay);
                }

                DockOverlayHitTestResult newResult = _overlay.DockOverlay.DockHitTest(mousePos);
                if (newResult != DockOverlayHitTestResult.None) {
                    if (newResult != _result && !disable) {
                        RemoveHint();

                        _result = newResult;
                        AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);

                        double maxSize = (_result == DockOverlayHitTestResult.Left || _result == DockOverlayHitTestResult.Right) ?
                            ActualWidth / 2 : ActualHeight / 2;
                        double proposedSize = window.Panel.GetDockSize();

                        _hint = new DockHintAdorner(this, _result, maxSize);
                        layer.Add(_hint);

                        return true;
                    }

                    return !disable;
                } else {
                    _result = newResult;
                    RemoveHint();
                    return false;
                }
            } else {
                RemoveHint();
                RemoveOverlay();

                return false;
            }
        }

        void IDropTarget.End(FloatingWindow window) {
            RemoveHint();
            RemoveOverlay();
        }

        void IDropTarget.PerformDock(FloatingWindow window, Point mousePos) {
            ((IDockHierarchyObjectHost)window).RemoveChild(window.Panel);
            window.Panel.Floating = false;

            if (_result == DockOverlayHitTestResult.None)
                return;

            if (_result == DockOverlayHitTestResult.Center) {
                DockWindow[] windows = window.Panel.GetAllDockWindows();
                foreach (DockWindow dockWindow in windows) {
                    dockWindow.ItemsParent.Items.Remove(dockWindow);
                    Items.Add(dockWindow);
                }
            } else {
                SplitPanel parent = Parent as SplitPanel;
                if (parent != null) {
                    parent.MergeSplitPanel(window.Panel, this, _result);
                }
            }
        }
        #endregion
    }
}
 